home *** CD-ROM | disk | FTP | other *** search
/ PC World Interactive 7 / PC World Interactive 7.iso / pcgames / EMERGY / DX5SDK / SDK / SAMPLES / MSTREAM / readme.txt
Text File  |  1997-07-14  |  9KB  |  155 lines

  1.  
  2.                                    MSTREAM
  3.  
  4.                         Design Description and Notes
  5.  
  6.    The MSTREAM sample application is an example of how you can use the new
  7. midiStream API that's built into Windows 95 to play MIDI data with low
  8. latency and low processor overhead.
  9.  
  10.    The basic idea behind the implementation is to start the application and
  11. initialize the user interface.  After that's done, the following events occur.
  12.  
  13. When a user selects and opens a file
  14. ------------------------------------
  15. Open a MIDI output device (the Microsoft MIDI Mapper in this case)
  16. Open the file using buffered I/O and fill our buffers with data in MIDI stream
  17.    format
  18. Prepare and queue the buffers using midiOutPrepareHeader and midiStreamOut
  19. Wait until the user decides to do something else
  20.  
  21. When PLAY is selected
  22. ---------------------
  23. Call midiStreamRestart to un-pause the device and begin playback
  24. As buffers are returned, the callback function fills them with more data and
  25. sends them back to be played by calling midiStreamOut() again.  IT IS
  26. IMPORTANT THAT YOU NOTE THAT IT IS NOT NECESSARY FOR YOU TO UNPREPARE AND
  27. PREPARE BUFFERS EVERY TIME THEY ARE RETURNED.  This is only a waste of time.
  28. All you have to do is send them back into the subsystem with midiStreamOut().
  29. The API documentation is a bit confusing on this point.
  30.  
  31. When PAUSE is selected
  32. ----------------------
  33. Call midiStreamPause() and wait for a PLAY or PAUSE to call midiStreamRestart().
  34. Note that pausing may make your audio sound kind of funny, since all notes are
  35. turned off and some note on events may be lost when playback is restarted.
  36.  
  37. When STOP is selected
  38. ---------------------
  39. Call midiStreamStop().  Since the callback function is in another thread, we
  40. use a Win32 synchronization object, an event to block the main thread until
  41. the callback thread has received all buffers.  When this happens, we know it's
  42. okay to go ahead and call midiStreamReset() and then to go ahead and free all
  43. our buffers.  Currently, the device is also closed using midiStreamClose().
  44. Then, if we are resetting the file to playback starting at its beginning point
  45. the next time PLAY is hit, reopen the file by calling StreamBufferSetup(),
  46. which is the workhorse function for opening a file and initializing the
  47. the converter and the buffers.
  48.  
  49. Note that the reason we must close and reopen the device is due to an apparent
  50. bug in the Multimedia System which causes undesireable playback once a device
  51. has been stopped.  If you disable the code for closing the device, then the rest
  52. of the code will automatically know it does not have to reopen the device.
  53. However, the following may occur: After the first instance of playback followed
  54. by a midiStreamStop() and a midiStreamReset(), there will be a pause equal in
  55. length to the first playback period before playback begins once more with a call
  56. to midiStreamRestart().
  57.  
  58. If Looped is selected
  59. ---------------------
  60. Notice that the converter has a little function in it called RewindConverter()
  61. which is called by ConvertToBuffer() if the bLooped variable is TRUE.  This
  62. function resets the track state structures and performs most of the steps
  63. originally executed in ConvertInit() with the notable exception of opening the
  64. file and reading in file and track header data.  It simply resets the tracks
  65. to their initial state.
  66.  
  67.  
  68. More on the buffering scheme
  69. ----------------------------
  70. Note that there is an OUT_BUFFER_SIZE and a BUFFER_TIME_LENGTH.  The idea is
  71. that the converter counts ticks and calculates when it has put at least
  72. BUFFER_TIME_LENGTH milliseconds worth of events in the buffer.  At this point,
  73. it returns to the caller with a "full" buffer.  At the very worst, there will
  74. be as many events as can fit into OUT_BUFFER_SIZE bytes.  Imagine that you have
  75. to MIDI files loaded into memory somehow and you want to switch between them in
  76. a hurry to correspond to some action the player performed like switching rooms.
  77. All you have to do is start filling the next buffer with new data (assuming
  78. they streams use similar patch sets, time division settings, etc.) and after
  79. (NUM_STREAM_BUFFERS-1)*BUFFER_TIME_LENGTH milliseconds, the music will switch
  80. over automatically.  This theory is sort of illustrated by the tempo trackbar
  81. control in this sample.  This control sets a flag which forces the converter
  82. code to start a new buffer, with the first event being a new tempo setting.
  83. The tempo setting is calculated as a relative increase with respect to the
  84. last real tempo event from the file.  Of course, to implement the scheme
  85. mentioned above requires some modification to the converter code so that it
  86. will work with multiple MIDI files.
  87.  
  88. Since the buffering scheme uses very small buffers, it is currently rather
  89. sensitive to heavy activity which may prevent it from completing processing
  90. in time.  This can be solved by increasing the NUM_STREAM_BUFFERS constant,
  91. but you must make a trade-off between latency and playback stability.
  92.  
  93.  
  94. Known problems and possible improvements:
  95. -----------------------------------------
  96. It is more desireable to enumerate all possible MIDI output devices and then
  97.    either allow the user to use a specific device, or choose one which has
  98.    desireable capabilities.  It is not recommended that you ship a product
  99.    which is hard-coded to use the MIDI Mapper only.  For more on enumerating
  100.    MIDI output devices, see the MIDIPLYR sample application which is part of
  101.    the Win32 SDK.
  102.  
  103. Instead of using the BUFFER_TIME_LENGTH, it would be possible to handle the
  104.    time signature META event in MIDI files and calculate the length of a
  105.    measure of music.  Then you could change buffers at the end of each measure,
  106.    which would probably yield a smoother sounding transition.  It may even be
  107.    possible to define system-exclusive events or other such extensions to the
  108.    MIDI converter code designed to provide your application with extra data
  109.    about when to switch between buffers or do other processing, though it is
  110.    not necessarily recommended that you modify the MIDI file format spec.  For
  111.    more information on that spec, contact the International MIDI Association.
  112.  
  113. You may wish to modify the way a change in tempo is handled, or remove this
  114.    code entirely.  Right now, there is a chunk of code in the convert function
  115.    AddEventToStreamBuffer() which detects tempo events and stores the new tempo.
  116.    There is also code which will react to the tempo slider by calculating a new
  117.    tempo, truncating the current buffer, and starting the next buffer with a
  118.    tempo event reflecting the new desired tempo. It may be more desireable to
  119.    force any tempo changes which are not encoded in the file originally to take
  120.    effect only on buffer boundaries, instead of always creating a buffer
  121.    boundary.  Proceeding under the above context of buffers equal in length to
  122.    measures of music, it may make more sense to only change tempo between each
  123.    measure.  You can also send tempo change messages using the midiOutShortMsg()
  124.    function, similar to the way SetAllChannelVolumes() behaves.
  125.  
  126. The volume control is a channel-wide, percentage-based control which relies on
  127.    a cache of volumes for each channel.  As the converter encounters a volume
  128.    change message, it flags it for a callback.  This causes the MidiProc() to
  129.    receive notification when that event is reached.  MidiProc() then grabs a
  130.    copy of the new volume event and sends a MIDI short message to the proper
  131.    channel which reflects the current slider position.  In other words, the
  132.    code saves the "full" or "raw" value and then modifies it so the volume
  133.    trackbar represents a percentage of that volume.  Though it is not shown
  134.    here, this scheme could be broken down to allow for individual volume
  135.    control also.  This idea could also be expanded to include the LSB volume
  136.    controller(39), which is not handled here.
  137.  
  138. Further, by duplicating the volume code described above and making slight
  139.    modifications to the converter (to detect other events), it is possible to
  140.    handle pan, balance, or other controller messages using the exact same idea.
  141.  
  142. Having said the above, it should be noted that attaching the volume change code
  143.    to a trackbar is for illustration purposes only.  The implementation shown
  144.    and described works best for isolated volume events, like when your player
  145.    moves away from the sound source and you need to update volume.  It should
  146.    not really be used for real-time scrolling because the method tends to flood
  147.    the MIDI output device with short messages, which interferes severly with
  148.    playback.
  149.  
  150. BUG: If you are using an internal MIDI device which uses the OPL chipset, you
  151.    should be aware of a bug which seems to occur in most of these drivers.  If
  152.    a volume channel message is sent to these drivers, they will not reflect the
  153.    change until a note on/off event occurs.  This means long sustaining notes
  154.    will not reduce in volume.
  155.